from pathlib import Path
import pymysql
from YkPywebview import YkWebviewApi, start
import paramiko
import socket
import time
import main_achieve as ma
import tkinter as tk
from tkinter import filedialog


class Api(YkWebviewApi):
    def __init__(self) -> None:
        super().__init__()

    def testConnection(self, serverConfig: dict) -> dict:
        """
        测试SSH连接是否可用
        :param serverConfig: 服务器配置字典
        :return: 返回包含连接状态的字典 {'suc': bool, 'msg': str}
        """
        ssh = None
        try:
            ssh = self.ssh_connect()

            stdin, stdout, stderr = ssh.exec_command('echo "Connection test"')
            if stdout.channel.recv_exit_status() == 0:
                self.setTaskBar(
                    title=f"SSH连接成功，目标服务器为：{serverConfig['ip']}", progress=0)
                return {"suc": True, "msg": "连接成功"}
            return {"suc": False, "msg": "命令执行失败"}

        except Exception as e:
            self.printToTerm(f"错误信息: {str(e)}", 'error')
            return {'suc': False, 'msg': f"错误信息{e}"}
        finally:
            if ssh:
                ssh.close()

    def testMySQLConnection(self, db_config):
        """
        测试 MySQL 数据库连接
        :param db_config: {host, port, username, password}
        :return: {"suc": True/False, "msg": "..."}
        """
        self.printToTerm("=== 开始测试数据库连接 ===")
        try:
            conn = pymysql.connect(
                host=db_config['host'],
                port=int(db_config['port']),
                user=db_config['username'],
                password=db_config['password'],
                connect_timeout=5
            )
            self.printToTerm("✅ 数据库连接成功")
            conn.close()
            return {'suc': True, 'msg': '连接成功'}
        except pymysql.err.OperationalError as e:
            error_msg = f"❌ 数据库连接失败: {str(e)}"
            self.printToTerm(error_msg)
            return {'suc': False, 'msg': error_msg}
        except Exception as e:
            error_msg = f"❌ 发生未知错误: {str(e)}"
            self.printToTerm(error_msg)
            return {'suc': False, 'msg': error_msg}

    def migrateMySQL(self, source, target):
        """
        数据迁移：从源数据库 dump 并导入目标数据库
        :param source: 源数据库配置
        :param target: 目标数据库配置（支持 useDocker 和 containerName）
        :return: {"suc": True/False, "msg": "..."}
        """
        self.printToTerm("=== 开始执行数据库迁移 ===")
        self.setTaskBar("正在执行数据库迁移...")
        if not all(k in source for k in ['host', 'port', 'username', 'password']):
            msg = "❌ 源数据库信息不完整"
            self.printToTerm(msg)
            return {'suc': False, 'msg': msg}

        if not all(k in target for k in ['host', 'port', 'username', 'password']):
            msg = "❌ 目标数据库信息不完整"
            self.printToTerm(msg)
            return {'suc': False, 'msg': msg}
        ssh = None
        try:
            # 创建SSH连接到目标服务器
            ssh = self.ssh_connect()
            user_name = self.getLinuxUserName()
            if target.get("useDocker", True):  # mysql运行在docker容器里
                container_name = target.get('containerName', 'mysql')
                # 检查容器是否存在
                stdin, stdout, stderr = ssh.exec_command(
                    f"docker ps -q -f name={container_name}"
                )
                if not stdout.read().decode().strip():
                    return {'suc': False, 'msg': f"❌ 容器 {container_name} 不存在或未运行"}

                # 1. 生成迁移命令
                migrate_content = f"""#!/bin/bash
docker exec -i {container_name} sh -c 'mysqldump -h{source["host"]} -P{source["port"]} -u{source["username"]} -p"{source["password"]}" --single-transaction --quick --lock-tables=false --no-tablespaces --skip-comments --all-databases | mysql -u{target["username"]} -p"{target["password"]}" --verbose'
                """

                # docker exec -i mysql_container sh -c 'mysqldump -hsges.yangke.site -P3306 -uroot -p"YangKe.08" --single-transaction --quick --lock-tables=false --all-databases | mysql -uroot -p"YangKe.08" --verbose'
            else:
                migrate_content = f"""#!/bin/bash
mysqldump -h{source["host"]} -P{source["port"]} \\
-u{source["username"]} -p"{source["password"]}" \\
--single-transaction --quick --lock-tables=false --no-tablespaces --skip-comments --all-databases | \\
mysql -h{target["host"]} -P{target["port"]} \\
-u{target["username"]} -p"{target["password"]}" --verbose
                """

            # 2. 上传脚本到服务器
            script_path = f"/home/{user_name}/migrate.sh"
            sftp = ssh.open_sftp()
            ma.remove_remote_file(sftp, script_path)
            with sftp.file(script_path, 'w') as remote_file:
                remote_file.write(migrate_content)
            sftp.close()

            # 3. 执行脚本
            self.printToTerm(f"已生成迁移脚本：\n{migrate_content}", 'debug')
            channel = ssh.get_transport().open_session(timeout=36000)
            channel.exec_command(f"chmod +x {script_path} && {script_path}")
            # 添加心跳检测
            channel.set_combine_stderr(True)
            self.openTerminal()
            # 4. 实时输出处理
            while True:
                if channel.exit_status_ready():
                    break
                if channel.recv_ready():
                    output = channel.recv(1024).decode()
                    if output:
                        self.printToTerm(output.strip())
                if channel.recv_stderr_ready():
                    error = channel.recv_stderr(1024).decode()
                    if error:
                        self.printToTerm(f"[错误] {error.strip()}", 'error')
                time.sleep(1)

            # 5. 清理脚本
            channel.exec_command(f"rm -f {script_path}")

            exit_status = channel.recv_exit_status()
            if exit_status == 0:
                msg = "✅ 数据库迁移成功"
                self.printToTerm(msg)
                return {'suc': True, 'msg': msg}
            else:
                error_msg = "❌ 数据库迁移失败"
                self.printToTerm(error_msg)
                return {'suc': False, 'msg': error_msg}

        except Exception as e:
            self.printToTerm(f"迁移异常: {str(e)}", 'error')
            return {'suc': False, 'msg': f"迁移异常: {str(e)}"}
        finally:
            self.setTaskBar("就绪")
            # 清理临时文件
            if ssh:
                ssh.close()

    def clearMySQLDatabase(self, target):
        """
        删除目标数据库中所有非系统数据库和用户
        :param target: 目标数据库配置
        :return: {"suc": True/False, "msg": "..." }
        """
        self.printToTerm("=== 开始清空目标数据库 ===")

        if not target.get('host') or not target.get('port') or not target.get('username'):
            msg = "❌ 目标数据库信息不完整"
            self.printToTerm(msg)
            return {'suc': False, 'msg': msg}

        try:
            conn = pymysql.connect(
                host=target['host'],
                port=int(target['port']),
                user=target['username'],
                password=target['password'],
                connect_timeout=5
            )
            with conn.cursor() as cur:
                # 获取所有数据库
                cur.execute("SHOW DATABASES")
                all_dbs = [row[0] for row in cur.fetchall()]
                protected_dbs = {'information_schema', 'mysql', 'performance_schema', 'sys'}
                user_dbs = [db for db in all_dbs if db not in protected_dbs]

                # 删除数据库
                for db in user_dbs:
                    drop_sql = f"DROP DATABASE IF EXISTS `{db}`;"
                    self.printToTerm(f"执行 SQL: {drop_sql}")
                    cur.execute(drop_sql)

                # 获取所有用户
                cur.execute("SELECT User FROM mysql.user WHERE User NOT IN ('root', 'mysql.sys', 'admin')")
                users = [row[0] for row in cur.fetchall()]

                # 删除用户
                for user in users:
                    cur.execute(f"DROP USER IF EXISTS '{user}'@'%'")
                    cur.execute(f"DROP USER IF EXISTS '{user}'@'localhost'")
                    self.printToTerm(f"删除用户: {user}")

                conn.commit()
                self.printToTerm("✅ 目标数据库已清空")
                return {'suc': True, 'msg': '目标数据库已清空'}

        except pymysql.err.OperationalError as e:
            error_msg = f"❌ 数据库操作失败: {str(e)}"
            self.printToTerm(error_msg)
            return {'suc': False, 'msg': error_msg}
        except Exception as e:
            error_msg = f"❌ 发生未知错误: {str(e)}"
            self.printToTerm(error_msg)
            return {'suc': False, 'msg': error_msg}

    def create_remote_folder(self, remote_path: str | Path, ssh=None):

        ma.create_remote_folder(remote_path, self.settings, ssh)

    def write_remote_file(self, remote_file_path: Path | str, content: str, ssh=None, append: bool = False) -> dict:
        """
        将字符串内容写入服务器上的指定文件

        :param remote_file_path: 远程文件路径(可以是Path对象或字符串)
        :param content: 要写入的文件内容
        :param ssh: 可选的SSH连接对象，为空时会自动创建
        :param append: 是否追加内容(True)或覆盖文件(False)
        :return: 包含操作结果的字典 {
            'suc': bool,  # 是否成功
            'msg': str,   # 状态消息
            'path': str   # 实际写入的路径
        }
        """
        sftp = None
        need_close = False
        try:
            if ssh is None:
                ssh = self.ssh_connect()
                need_close = True

            # 统一路径格式
            remote_file_path = str(remote_file_path).replace('\\', '/')
            mode = 'a' if append else 'w'

            # 使用SFTP写入文件
            sftp = ssh.open_sftp()

            # 创建父目录（如果不存在）
            parent_dir = str(Path(remote_file_path).parent)
            try:
                sftp.stat(parent_dir)  # 检查目录是否存在
            except FileNotFoundError:
                self.create_remote_folder(parent_dir, ssh)  # 创建目录
                self.printToTerm(f"已创建目录: {parent_dir}", 'debug')

            with sftp.open(remote_file_path, mode) as remote_file:
                remote_file.write(content)

            return {
                'suc': True,
                'msg': f'成功{"追加" if append else "写入"}文件 {remote_file_path}',
                'path': remote_file_path
            }

        except Exception as e:
            return {
                'suc': False,
                'msg': f'文件操作失败: {str(e)}',
                'path': str(remote_file_path)
            }
        finally:
            if sftp:
                sftp.close()
            if ssh and need_close:
                ssh.close()

    def setExecWithNoPassword(self, serverConfig: dict) -> dict:
        """
        检查并设置当前用户执行sudo命令是否需要密码
        :param serverConfig: 服务器配置字典
        :return: 返回包含操作结果的字典 {'suc': bool, 'needPassword': bool, 'msg': str}
        """
        if serverConfig['username'] == 'root':
            return {"suc": True, "needPassword": False, "msg": "root用户执行sudo命令不需要密码"}
        ssh = None
        try:
            ssh = self.ssh_connect()

            # 1. 先检查当前是否需要密码
            stdin, stdout, stderr = ssh.exec_command(
                f'sudo grep -E "^\\s*{serverConfig["username"]}\\s+" /etc/sudoers || '
                f'sudo grep -E "^\\s*%{serverConfig["username"]}\\s+" /etc/sudoers.d/* || '
                f'sudo grep -E "^\\s*{serverConfig["username"]}\\s+" /etc/sudoers.d/{serverConfig["username"]}-nopasswd'
            )
            output = stdout.read().decode().strip()

            if "NOPASSWD" in output:
                return {"suc": True, "needPassword": False, "msg": "当前用户执行sudo命令不需要密码"}

            # 2. 如果需要密码，则设置NOPASSWD
            self.printToTerm("检测到需要密码，正在设置无密码sudo权限...", 'info')

            # 创建sudoers.d目录下的配置文件
            sudo_config = f"{serverConfig['username']} ALL=(ALL) NOPASSWD:ALL"
            # 修改为分开执行命令，确保每个sudo都能获取密码
            commands = [
                f'echo "{sudo_config}" | sudo tee /etc/sudoers.d/{serverConfig["username"]}-nopasswd',
                'sudo chmod 440 /etc/sudoers.d/*',
                'sudo visudo -c'
            ]

            for cmd in commands:
                channel = ssh.invoke_shell()
                channel.send((cmd + "\n").encode('utf-8'))
                while not channel.recv_ready():
                    time.sleep(0.1)
                output = channel.recv(1024).decode()
                if "[sudo] password for" in output:
                    while not channel.exit_status_ready():
                        channel.send(serverConfig["password"] + "\n")
                        time.sleep(0.1)
            # 3. 验证设置是否成功
            stdin, stdout, stderr = ssh.exec_command(
                f'sudo grep -E "^\\s*{serverConfig["username"]}\\s+" /etc/sudoers.d/{serverConfig["username"]}-nopasswd'
            )

            verify_output = stdout.read().decode().strip()

            if "NOPASSWD" in verify_output:
                return {"suc": True, "needPassword": False, "msg": "已成功设置当前用户执行sudo命令不需要密码"}
            else:
                return {"suc": False, "needPassword": True, "msg": "设置无密码sudo失败，请检查sudoers配置"}

        except Exception as e:
            return {"suc": False, "needPassword": True, "msg": f"未知错误: {str(e)}"}
        finally:
            if ssh:
                ssh.close()

    def getServerConfig(self):
        if self.settings and 'serverConfig' in self.settings:
            return self.settings['serverConfig']
        else:
            return {}

    def getLinuxUserName(self) -> str | None:
        """
        获取登录服务器的用户名
        :param serverConfig: 服务器配置字典
        :return: 返回用户名字符串
        """
        return self.getServerConfig().get('username')

    def selectedFolder(self):
        """
        在现代浏览器中，由于安全和隐私的原因，前端 JavaScript 无法直接获取用户选择文件夹的绝对路径。这种限制是为了保护用户的文件系统信息，防止恶意网站获取用户的敏感信息。
        为了获得绝对路径，通过python获取。
        """
        # 创建一个 Tkinter 窗口（隐藏主窗口）
        root = tk.Tk()
        root.withdraw()  # 隐藏主窗口

        # 弹出选择文件夹的对话框
        try:
            folder_path = filedialog.askdirectory(title="请选择一个文件夹")
            return folder_path
        finally:
            root.destroy()  # 确保窗口被销毁

    def ssh_connect(self) -> paramiko.SSHClient:
        """
        连接ssh服务器
        """
        return ma.ssh_connect(self.settings)

    def createUserFolder(self):
        """
        在服务器的/home目录下创建用户目录（即以用户名命名的目录），如果用户目录存在则不做任何操作，如果创建失败，提示失败原因
        :return: 返回操作结果字典 {'suc': bool, 'msg': str}
        """
        return ma.createUserFolder(self.settings)

    def installDocker(self, serverConfig: dict) -> dict:
        """
        在远程服务器上安装Docker，如果服务器上没有ssh，需要先安装ssh
        sudo apt update && sudo apt install openssh-server -y
        sudo systemctl status ssh
        sudo systemctl enable ssh

        :param serverConfig: 服务器配置字典
        :return: 返回包含安装状态的字典 {'suc': bool, 'msg': str}
        """
        self.setExecWithNoPassword(serverConfig)  # 检查并设置无密码sudo权限
        self.openTerminal()  # 打开终端抽屉，用于显示安装进度
        ssh = None
        try:
            self.printToTerm("开始建立SSH连接...", 'info')
            ssh = self.ssh_connect()
            self.printToTerm("SSH连接成功", 'info')

            # 显示服务器名称
            stdin, stdout, stderr = ssh.exec_command('hostname')
            hostname = stdout.read().decode().strip()
            self.printToTerm(f"连接服务器: {hostname}", 'info')

            # 0. 先检查Docker是否已安装
            self.printToTerm("正在检查Docker是否已安装...", 'info')
            stdin, stdout, stderr = ssh.exec_command(
                'command -v docker && docker --version')
            docker_check = stdout.read().decode().strip()
            if docker_check:
                self.printToTerm(f"检测到Docker已安装:\n{docker_check}", 'info')
                self.setTaskBar(title="Docker已安装！", progress=0)
                return {"suc": True, "msg": "Docker已安装"}

            # 1. 先检测操作系统类型
            self.printToTerm("正在检测操作系统类型...", 'info')
            try:
                stdin, stdout, stderr = ssh.exec_command('echo $SHELL')
                shell_type = stdout.read().decode().strip()
                # 先执行简单的uname命令测试
                stdin, stdout, stderr = ssh.exec_command('uname -a')
                os_info = stdout.read().decode(errors='ignore').lower()
                if not os_info:
                    self.printToTerm("错误：无法获取系统信息", 'error')
                    return {"suc": False, "msg": "无法获取系统信息"}

                self.printToTerm(f"检测到系统信息: {os_info[:200]}...", 'debug')

                # 根据uname结果判断系统类型
                if 'linux' in os_info:
                    if 'ubuntu' in os_info or 'debian' in os_info:
                        os_type = 'ubuntu'
                    elif 'centos' in os_info or 'redhat' in os_info or 'rhel' in os_info:
                        os_type = 'centos'
                    else:
                        os_type = 'unknown'
                else:
                    self.printToTerm("错误：不支持的操作系统", 'error')
                    return {"suc": False, "msg": "不支持的操作系统"}

            except Exception as e:
                self.printToTerm(f"获取操作系统信息失败: {str(e)}", 'error')
                return {"suc": False, "msg": f"获取操作系统信息失败: {str(e)}"}

            # 2. 根据操作系统类型选择不同的安装命令
            if 'ubuntu' in os_info or 'debian' in os_info:
                self.printToTerm("检测到Ubuntu/Debian系统，使用apt安装Docker", 'debug')
                # 添加版本检查
                stdin, stdout, stderr = ssh.exec_command('lsb_release -a')
                ubuntu_info = stdout.read().decode()
                self.printToTerm(f"系统版本信息:\n{ubuntu_info}", 'info')
                # 测试主机名解析是否正常
                stdin, stdout, stderr = ssh.exec_command('sudo echo test')
                test_output = stderr.read().decode()
                commands = []

                # 只有在出现主机名解析错误时才添加修复命令
                if 'unable to resolve host' in test_output:
                    commands.extend([
                        (f'sudo sed -i "s/127.0.0.1 localhost/127.0.0.1 localhost {hostname}/" /etc/hosts',
                         "修复主机名解析..."),
                        (f'sudo hostnamectl set-hostname {hostname}', "设置主机名...")
                    ])
                commands.extend([
                    # 修改为先检查服务是否存在再停止
                    ('if systemctl list-unit-files | grep -q unattended-upgrades; then sudo systemctl stop unattended-upgrades && sudo systemctl disable unattended-upgrades; fi',
                     "停止自动更新服务..."),
                    ('sudo killall apt apt-get || true', "终止可能存在的apt进程..."),
                    ('sudo rm -f /var/lib/apt/lists/lock /var/cache/apt/archives/lock /var/lib/dpkg/lock*',
                     "清理旧的锁文件..."),
                    ('sudo dpkg --configure -a', "修复可能的dpkg问题..."),
                    ('sudo apt-get update || echo "警告：更新软件包列表失败"', "尝试更新软件包列表(忽略失败)"),
                    ('sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common gnupg',
                     "正在安装依赖包..."),
                    # 添加国内镜像源
                    ('curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --yes --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg',
                     "添加阿里云Docker GPG密钥..."),
                    ('echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null',
                     "添加阿里云Docker软件源..."),
                    ('sudo apt-get update || echo "警告：更新软件包列表失败"', "尝试更新软件包列表(忽略失败)"),
                    ('sudo apt-get install -y docker-ce docker-ce-cli containerd.io',
                     "安装Docker引擎..."),
                    # 配置Docker国内镜像加速
                    ('sudo mkdir -p /etc/docker', "创建Docker配置目录..."),
                    ('echo \'{"registry-mirrors": ["https://docker.1ms.run", "https://docker.xuanyuan.me"]}\' | sudo tee /etc/docker/daemon.json',
                     "配置Docker国内镜像加速..."),
                    ('sudo systemctl enable docker', "设置Docker开机启动..."),
                    ('sudo systemctl restart docker', "重启Docker服务..."),
                    ('sudo usermod -aG docker $USER', "将当前用户加入docker组..."),
                    ('if systemctl list-unit-files | grep -q unattended-upgrades; then sudo systemctl enable unattended-upgrades && sudo systemctl start unattended-upgrades; fi',
                     "恢复自动更新服务..."),
                ])
            elif 'centos' in os_info or 'rhel' in os_info or 'fedora' in os_info:
                self.printToTerm("检测到CentOS/RHEL系统，使用yum安装Docker", 'info')
                commands = [
                    ('sudo yum install -y yum-utils', "安装yum工具..."),
                    ('sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo',
                     "添加阿里云Docker软件源..."),
                    ('sudo yum install -y docker-ce docker-ce-cli containerd.io',
                     "安装Docker引擎..."),
                    ('sudo mkdir -p /etc/docker', "创建Docker配置目录..."),
                    ('echo \'{"registry-mirrors": ["https://registry.docker-cn.com", "https://hub-mirror.c.163.com", "https://mirror.baidubce.com"]}\' | sudo tee /etc/docker/daemon.json',
                     "配置Docker国内镜像加速..."),
                    ('sudo systemctl enable docker', "设置Docker开机启动..."),
                    ('sudo systemctl restart docker', "重启Docker服务..."),
                    ('sudo usermod -aG docker $USER', "将当前用户加入docker组...")
                ]
            else:
                self.printToTerm("错误：不支持的操作系统类型")
                return {"suc": False, "msg": "不支持的操作系统类型"}

            # 3. 执行安装命令
            for cmd, desc in commands:
                self.printToTerm(f"=== 开始执行: {desc} ===", 'debug')
                self.printToTerm(f"执行命令: {cmd}", 'debug')
                # 修改为在exec_command后立即检查连接状态
                stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True)
                # 实时读取输出
                ma.print_debug_info(self, stdout, stderr)

                exit_status = stdout.channel.recv_exit_status()
                if exit_status != 0:
                    error_msg = stderr.read().decode()
                    self.printToTerm(f"=== 执行失败: {desc} ===", 'error')
                    self.printToTerm(f"错误详情: {error_msg}", 'error')
                    return {"suc": False, "msg": f"命令执行失败: {cmd}, 错误: {error_msg}"}
                else:
                    self.printToTerm(f"=== 完成: {desc} ===")
            self.printToTerm("Docker安装完成")
            self.setTaskBar(title="Docker安装成功！", progress=0)
            return {"suc": True, "msg": "Docker安装成功"}

        except Exception as e:
            self.printToTerm(f"错误：未知异常 - {str(e)}")
            return {"suc": False, "msg": f"未知错误: {str(e)}"}
        finally:
            if ssh:
                ssh.close()
            self.printToTerm("SSH连接已关闭")
            self.setTaskBar("就绪")

    def getDockerImages(self) -> dict:
        """
        获取服务器上已安装的Docker镜像列表
        :param serverConfig: 服务器配置字典
        :return: 返回包含镜像列表的字典 {'suc': bool, 'images': list, 'msg': str}
        """
        if self.settings and 'serverConfig' in self.settings:
            serverConfig = self.settings['serverConfig']
        else:
            return {'suc': False, 'images': [], 'msg': '无法获取服务器信息，请检查服务器配置！'}
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        try:
            if serverConfig['authType'] == 'password':
                ssh.connect(
                    hostname=serverConfig['ip'],
                    port=int(serverConfig['port']),
                    username=serverConfig['username'],
                    password=serverConfig['password'],
                    timeout=10
                )
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key_file(
                    serverConfig['privateKey']
                )
                ssh.connect(
                    hostname=serverConfig['ip'],
                    port=int(serverConfig['port']),
                    username=serverConfig['username'],
                    pkey=private_key,
                    timeout=10
                )

            # 修改为获取更详细的镜像信息
            stdin, stdout, stderr = ssh.exec_command(
                'docker images --format "{{.Repository}}|{{.Tag}}|{{.ID}}|{{.Size}}"')
            images = []
            for line in stdout.read().decode().splitlines():
                if line and '<none>' not in line:
                    repo, tag, image_id, size = line.split('|')
                    images.append({
                        'name': repo,
                        'tag': tag,
                        'id': image_id,
                        'size': size
                    })
            self.setTaskBar(f"获取镜像列表成功")
            return {
                'suc': True,
                'images': images,
                'msg': '获取镜像列表成功'
            }

        except paramiko.AuthenticationException:
            return {"suc": False, "images": [], "msg": "认证失败，请检查用户名和密码/密钥"}
        except paramiko.SSHException as e:
            return {"suc": False, "images": [], "msg": f"SSH连接错误: {str(e)}"}
        except socket.timeout:
            return {"suc": False, "images": [], "msg": "连接超时，请检查网络或服务器是否可达"}
        except Exception as e:
            return {"suc": False, "images": [], "msg": f"获取镜像列表失败: {str(e)}"}
        finally:
            ssh.close()

    def searchDockerImages(self, keyword: str) -> dict:
        """
        搜索Docker Hub上的镜像
        :param keyword: 搜索关键词
        :return: 返回包含搜索结果的字典 {'suc': bool, 'results': list, 'msg': str}
        """
        if self.settings and 'serverConfig' in self.settings:
            serverConfig = self.settings['serverConfig']
        else:
            return {'suc': False, 'images': [], 'msg': '无法获取服务器信息，请检查服务器配置！'}
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        try:
            if serverConfig['authType'] == 'password':
                ssh.connect(
                    hostname=serverConfig['ip'],
                    port=int(serverConfig['port']),
                    username=serverConfig['username'],
                    password=serverConfig['password'],
                    timeout=30  # 增加超时时间
                )
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key_file(
                    serverConfig['privateKey']
                )
                ssh.connect(
                    hostname=serverConfig['ip'],
                    port=int(serverConfig['port']),
                    username=serverConfig['username'],
                    pkey=private_key,
                    timeout=30
                )

            # 执行docker search命令并实时输出
            self.printToTerm(f"正在搜索Docker镜像: {keyword}...", 'info')
            stdin, stdout, stderr = ssh.exec_command(
                f'docker search {keyword}', get_pty=True)

            # 实时读取输出
            output_lines = []
            while not stdout.channel.exit_status_ready():
                if stdout.channel.recv_ready():
                    output = stdout.channel.recv(1024).decode()
                    if output:
                        self.printToTerm(output.strip())
                        output_lines.append(output.strip())
                if stderr.channel.recv_stderr_ready():
                    error = stderr.channel.recv_stderr(1024).decode()
                    if error:
                        self.printToTerm(f"[错误] {error.strip()}")

            # 处理最终结果
            exit_status = stdout.channel.recv_exit_status()
            if exit_status != 0:
                error_msg = stderr.read().decode()
                self.printToTerm(f"搜索失败: {error_msg}")
                return {"suc": False, "results": [], "msg": f"搜索失败: {error_msg}"}

            # 解析结果
            full_output = ''.join(output_lines)
            results = []
            if full_output:
                lines = full_output.split('\n')
                if len(lines) > 1:  # 第一行是标题行
                    results = [line.split()[0]
                               for line in lines[1:] if line.strip()]

            self.printToTerm(f"搜索完成，找到{len(results)}个结果", 'info')
            return {
                'suc': True,
                'results': results,
                'msg': '搜索成功'
            }

        except paramiko.AuthenticationException:
            return {"suc": False, "results": [], "msg": "认证失败，请检查用户名和密码/密钥"}
        except paramiko.SSHException as e:
            return {"suc": False, "results": [], "msg": f"SSH连接错误: {str(e)}"}
        except socket.timeout:
            return {"suc": False, "results": [], "msg": "连接超时，请检查网络或服务器是否可达"}
        except Exception as e:
            return {"suc": False, "results": [], "msg": f"搜索镜像失败: {str(e)}"}
        finally:
            ssh.close()

    def installDockerImage(self, imageName: str, ssh=None) -> dict:
        """
        在远程服务器上安装指定的Docker镜像
        :param imageName: 要安装的镜像名称
        :param ssh:
        :return: 返回包含安装状态的字典 {'suc': bool, 'msg': str}
        """
        try:
            mirrors = self.settings['docker']['mirrors']
        except:
            self.printToTerm(f"请先设置Docker镜像源")
            return {"suc": False, "msg": "请先设置Docker镜像源"}
        need_close = False
        # 检查并提取镜像名称和标签
        if ':' in imageName:
            image, tag = imageName.split(':', 1)
        else:
            image = imageName
            tag = 'latest'  # 默认使用latest标签

        try:
            if ssh is None:
                ssh = self.ssh_connect()
                need_close = True

            # 先检查本地是否已经存在该镜像
            self.printToTerm(f"检查本地是否已存在镜像: {imageName}...", 'info')
            check_cmd = f"docker images -q {image}:{tag}"
            stdin, stdout, stderr = ssh.exec_command(check_cmd)
            image_id = stdout.read().decode().strip()

            if image_id:
                self.printToTerm(f"镜像 {imageName} 已存在，无需重新拉取", 'info')
                self.setTaskBar(f"镜像 {imageName} 已存在")
                return {"suc": True, "msg": f"镜像 {imageName} 已存在"}

            # 执行docker pull命令
            self.printToTerm(f"正在拉取镜像: {imageName}...", 'info')
            error_msg = ''
            for mirror in mirrors:
                self.printToTerm(f"尝试使用镜像源: {mirror}", 'debug')
                mirror = mirror.replace('https://', '').rstrip('/')
                stdin, stdout, stderr = ssh.exec_command(
                    f'docker pull {mirror}/{imageName}', get_pty=True)

                # 实时读取输出
                ma.print_debug_info(self, stdout, stderr)

                exit_status = stdout.channel.recv_exit_status()
                if exit_status != 0:
                    error_msg = stderr.read().decode()
                    self.printToTerm(f"镜像拉取失败: {error_msg}")
                    continue  # 尝试下一个镜像源
                else:
                    self.printToTerm(f"镜像 {imageName} 安装成功", 'info')
                    self.setTaskBar(f"镜像 {imageName} 安装成功")

                    # 重命名镜像名称，因为从镜像源拉取的镜像名称可能带有镜像源名
                    stdin, stdout, stderr = ssh.exec_command(
                        f'docker tag {mirror}/{image}:{tag} {image}:{tag}', get_pty=True)
                    # 实时读取输出
                    while not stdout.channel.exit_status_ready():
                        if stdout.channel.recv_ready():
                            output = stdout.channel.recv(1024).decode()
                            if output:
                                self.printToTerm(output.strip())
                        if stderr.channel.recv_stderr_ready():
                            error = stderr.channel.recv_stderr(1024).decode()
                            if error:
                                self.printToTerm(f"[错误] {error.strip()}")
                        exit_status = stdout.channel.recv_exit_status()

                    return {"suc": True, "msg": f"镜像 {imageName} 安装成功"}
            return {"suc": False, "msg": f"镜像拉取失败: {error_msg}"}

        except Exception as e:
            self.setTaskBar(f"未知错误: {str(e)}")
            return {"suc": False, "msg": f"未知错误: {str(e)}"}
        finally:
            if need_close and ssh:
                ssh.close()

    def remove_image(self, image: dict) -> dict:
        """
        删除服务器上的Docker镜像
        :param image: 镜像信息字典，应包含id字段
        :return: 返回操作结果字典 {'suc': bool, 'msg': str}
        """
        ssh = None
        try:
            ssh = self.ssh_connect()

            # 执行docker rmi命令删除镜像，使用-f参数强制删除被多个仓库引用的镜像
            image_id = image['id']
            self.printToTerm(f"正在删除镜像: {image_id}...", 'info')

            stdin, stdout, stderr = ssh.exec_command(f'docker rmi -f {image_id}', get_pty=True)

            # 实时读取输出
            ma.print_debug_info(self, stdout, stderr)

            exit_status = stdout.channel.recv_exit_status()
            if exit_status == 0:
                self.printToTerm(f"镜像 {image_id} 删除成功", 'info')
                self.setTaskBar(f"镜像 {image_id} 删除成功")
                return {"suc": True, "msg": f"镜像 {image_id} 删除成功"}
            else:
                error_msg = stderr.read().decode().strip()
                self.printToTerm(f"镜像删除失败: {error_msg}", 'error')
                return {"suc": False, "msg": f"镜像删除失败: {error_msg}"}

        except Exception as e:
            error_msg = f"删除镜像时发生错误: {str(e)}"
            self.printToTerm(error_msg, 'error')
            self.setTaskBar("删除镜像失败")
            return {"suc": False, "msg": error_msg}
        finally:
            if ssh:
                ssh.close()

    def rename_image(self, image: dict, new_name: str) -> dict:
        """
        重命名服务器上的Docker镜像
        :param image: 镜像信息字典，应包含id字段
        :param new_name: 新的镜像名称（格式：名称:标签）
        :return: 返回操作结果字典 {'suc': bool, 'msg': str}
        """
        ssh = None
        try:
            ssh = self.ssh_connect()

            # 执行docker tag命令重命名镜像
            image_id = image['id']
            self.printToTerm(f"正在重命名镜像: {image_id} -> {new_name}...", 'info')

            stdin, stdout, stderr = ssh.exec_command(f'docker tag {image_id} {new_name}', get_pty=True)

            # 实时读取输出
            ma.print_debug_info(self, stdout, stderr)

            exit_status = stdout.channel.recv_exit_status()
            if exit_status == 0:
                self.printToTerm(f"镜像 {image_id} 重命名为 {new_name} 成功", 'info')
                self.setTaskBar(f"镜像重命名成功")
                return {"suc": True, "msg": f"镜像重命名成功"}
            else:
                error_msg = stderr.read().decode().strip()
                self.printToTerm(f"镜像重命名失败: {error_msg}", 'error')
                return {"suc": False, "msg": f"镜像重命名失败: {error_msg}"}

        except Exception as e:
            error_msg = f"重命名镜像时发生错误: {str(e)}"
            self.printToTerm(error_msg, 'error')
            self.setTaskBar("重命名镜像失败")
            return {"suc": False, "msg": error_msg}
        finally:
            if ssh:
                ssh.close()

    def getRunningContainers(self, ssh=None, info_list=True, name_list=False, id_list=False) -> dict | list:
        """
        获取服务器上当前运行的Docker容器列表
        :return: 返回包含容器列表的字典 {'suc': bool, 'containers': list, 'msg': str}
        """
        info = ma.getRunningContainers(self.settings, ssh)
        if info_list:
            return info
        else:
            if name_list:
                return [container['name'] for container in info['containers']]
            elif id_list:
                return [container['id'] for container in info['containers']]
        return info

    def findAvailablePort(self, preferred_port=None, ssh=None) -> dict:
        """
        获取服务器上可用的端口
        :param preferred_port: 优先使用的端口
        :return: 返回包含可用端口的字典 {'suc': bool, 'port': int, 'msg': str}
        """
        return ma.findAvailablePort(self.settings, preferred_port, ssh)

    def stopContainer(self, containerId: str) -> dict:
        """
        停止并移除指定的Docker容器
        :param containerId: 要停止的容器ID
        :return: 返回包含操作结果的字典 {'suc': bool, 'msg': str}
        """
        return ma.stopContainer(self.settings, containerId)

    def logContainer(self, containerId: str):
        return ma.logContainer(self, containerId)

    def restartContainer(self, containerId: str):
        return ma.restartContainer(self, containerId)

    def reloadCaddyfile(self, containerId: str):
        print(containerId)
        return ma.reloadCaddyfile(self)

    def deployContainer(self, containerConfig: dict) -> dict:
        """
        部署Docker容器
        :param containerConfig: 容器配置字典，包含image, name, env, ports等参数
        :return: 返回包含部署结果的字典 {'suc': bool, 'msg': str}
        """
        if self.settings and 'serverConfig' in self.settings:
            serverConfig = self.settings['serverConfig']
        else:
            return {'suc': False, 'images': [], 'msg': '无法获取服务器信息，请检查服务器配置！'}
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        try:
            if serverConfig['authType'] == 'password':
                ssh.connect(
                    hostname=serverConfig['ip'],
                    port=int(serverConfig['port']),
                    username=serverConfig['username'],
                    password=serverConfig['password'],
                    timeout=30
                )
            else:  # key认证
                private_key = paramiko.RSAKey.from_private_key_file(
                    serverConfig['privateKey']
                )
                ssh.connect(
                    hostname=serverConfig['ip'],
                    port=int(serverConfig['port']),
                    username=serverConfig['username'],
                    pkey=private_key,
                    timeout=30
                )

            # 构建docker run命令
            cmd = f"docker run -d --name {containerConfig['name']}"

            # 添加环境变量
            if 'env' in containerConfig:
                for key, value in containerConfig['env'].items():
                    cmd += f" -e {key}={value}"

            # 添加端口映射
            if 'ports' in containerConfig:
                for host_port, container_port in containerConfig['ports'].items():
                    cmd += f" -p {host_port}:{container_port}"

            # 添加卷映射
            if 'volumes' in containerConfig:
                for host_path, container_path in containerConfig['volumes'].items():
                    cmd += f" -v {host_path}:{container_path}"

            # 添加容器链接
            if 'links' in containerConfig:
                for link in containerConfig['links']:
                    cmd += f" --link {link}"

            # 添加镜像名称
            cmd += f" {containerConfig['image']}"

            # 执行命令
            self.printToTerm(f"正在部署容器: {cmd}", 'info')
            stdin, stdout, stderr = ssh.exec_command(cmd, get_pty=True)

            # 实时读取输出
            while not stdout.channel.exit_status_ready():
                if stdout.channel.recv_ready():
                    output = stdout.channel.recv(1024).decode()
                    if output:
                        self.printToTerm(output.strip())
                if stderr.channel.recv_stderr_ready():
                    error = stderr.channel.recv_stderr(1024).decode()
                    if error:
                        self.printToTerm(f"[错误] {error.strip()}")

            exit_status = stdout.channel.recv_exit_status()
            if exit_status == 0:
                self.printToTerm(f"容器 {containerConfig['name']} 部署成功", 'info')
                return {"suc": True, "msg": f"容器 {containerConfig['name']} 部署成功"}
            else:
                error_msg = stderr.read().decode()
                self.printToTerm(f"容器部署失败: {error_msg}", 'error')
                return {"suc": False, "msg": f"容器部署失败: {error_msg}"}

        except paramiko.AuthenticationException:
            return {"suc": False, "msg": "认证失败，请检查用户名和密码/密钥"}
        except paramiko.SSHException as e:
            return {"suc": False, "msg": f"SSH连接错误: {str(e)}"}
        except socket.timeout:
            return {"suc": False, "msg": "连接超时，请检查网络或服务器是否可达"}
        except Exception as e:
            return {"suc": False, "msg": f"部署容器时发生未知错误: {str(e)}"}
        finally:
            ssh.close()

    def deployNextcloud(self, containerConfig: dict) -> dict:
        """
        部署Nextcloud容器
        :param containerConfig: 容器配置字典，包含image, name, env, ports等参数
        :return: 返回包含部署结果的字典 {'suc': bool,'msg': str}
        """
        return ma.deployNextCloud(self, containerConfig)

    def deployMySQL(self, containerConfig: dict, ssh=None) -> dict:
        """
        部署MySQL容器
        :param containerConfig: 容器配置字典，包含image, name, env, ports等参数
        :param ssh:
        :return: 返回包含部署结果的字典 {'suc': bool,'msg': str}
        """
        return ma.deployMySQL(self, containerConfig)

    def deployCaddy(self, info):
        return ma.deployCaddy(self, info)

    def deployDockerRegistryHub(self, containerConfig: dict, ssh=None):
        return ma.deployDockerRegistryHub(self, containerConfig, ssh)

    def deployLoginRegService(self, containerConfig: dict, ssh=None):
        return ma.deployLoginRegService(self, containerConfig, ssh)

    def uploadFile(self, localPath: str | Path, remotePath: str | Path, ssh=None) -> dict:
        """
        通过SSH上传文件到远程服务器
        :param localPath: 本地文件路径
        :param remotePath: 远程文件路径
        :param ssh: 如果传入ssh，则使用已有的ssh上传文件，避免重复建立ssh连接
        :return: 返回包含上传状态的字典 {'suc': bool, 'msg': str}
        """
        return ma.uploadFile(self.settings, Path(localPath), Path(remotePath), ssh)

    def uploadFolder(self, localPath: str | Path, remotePath: str | Path, ssh=None) -> dict:
        """
        通过SSH上传文件到远程服务器
        :param localPath: 本地文件路径
        :param remotePath: 远程文件路径
        :param ssh:
        :return: 返回包含上传状态的字典 {'suc': bool, 'msg': str}
        """
        return ma.uploadFolder(self.settings, Path(localPath), Path(remotePath), ssh)

    def find_cert_file(self, root_path, ssh=None) -> Path | None:
        need_close = False
        try:
            if ssh is None:
                ssh = self.ssh_connect()
                need_close = True

            sftp = ssh.open_sftp()
            root_path = str(root_path).replace('\\', '/')

            for entry in sftp.listdir_attr(root_path):
                # 检查.crt文件
                if entry.filename.endswith('.crt'):
                    return Path(root_path) / entry.filename

                # 检查是否是目录(使用SFTPAttributes的st_mode属性)
                if entry.st_mode & 0o040000:  # 0o040000表示目录
                    found = self.find_cert_file(Path(root_path) / entry.filename, ssh)
                    if found:
                        return found
            return None
        except Exception as e:
            print(f"错误信息{e}")
            return None
        finally:
            if need_close and ssh:
                ssh.close()

    def pushDockerImageToRegistry(self, image, ssh=None):
        """

        """
        need_close = False
        try:
            if ssh is None:
                ssh = self.ssh_connect()
                need_close = True

            # 获取目标registry地址
            registry_host = self.settings.get('QuickDeploy', {}).get('server_domain')
            registry_port = self.settings.get('QuickDeploy', {}).get('registry_server_port')
            image_id, image_name, image_tag = image['id'], image['name'], image['tag']
            mirrors = self.settings.get('docker', {}).get('mirrors')
            self_mirror = f"https://{registry_host}:{registry_port}"
            if self_mirror not in mirrors:
                mirrors.append(self_mirror)
                self.saveProjectSettings({'docker': {'mirrors': mirrors}})

            mirrors_no_prefix = [mirror.replace('https://', '').replace('http://', '') for mirror in mirrors]

            # 因为image_name中可能包含镜像名，上传时从 image_name 中删除掉镜像的路径，即mirrors_no_prefix中的前缀
            image_base_name = image_name
            for prefix in mirrors_no_prefix:
                if image_name.startswith(prefix):
                    image_base_name = image_name[len(prefix) + 1:]
                    break

            # 将ssh连接的服务器上的docker中的名称为image_name的镜像推送到docker registry，registry的地址为self.registry_url
            # 1. 给镜像打tag
            tag_command = f"docker tag {image_id} {registry_host}:{registry_port}/{image_base_name}:{image_tag}"
            self.printToTerm(f"执行命令: {tag_command}", 'debug')
            stdin, stdout, stderr = ssh.exec_command(tag_command)
            error = stderr.read().decode()
            if error:
                self.printToTerm(f"[错误] {error}", 'error')
                return {"suc": False, "msg": f"镜像tag失败: {error}"}

            # 2. 推送镜像到registry
            push_command = f"docker push {registry_host}:{registry_port}/{image_name}:{image_tag}"
            self.printToTerm(f"执行命令: {push_command}", 'debug')
            stdin, stdout, stderr = ssh.exec_command(push_command, get_pty=True)

            # 实时输出日志
            while not stdout.channel.exit_status_ready():
                if stdout.channel.recv_ready():
                    output = stdout.channel.recv(1024).decode()
                    if output:
                        self.printToTerm(output.strip())
                if stderr.channel.recv_stderr_ready():
                    error = stderr.channel.recv_stderr(1024).decode()
                    if error:
                        self.printToTerm(f"[错误] {error.strip()}", 'error')

            exit_status = stdout.channel.recv_exit_status()
            if exit_status != 0:
                error_msg = stderr.read().decode()
                self.printToTerm(f"镜像推送失败: {error_msg}", 'error')
                return {"suc": False, "msg": f"镜像推送失败: {error_msg}"}

            self.printToTerm(f"镜像 {image_name} 成功推送到 {registry_host}:{registry_port}", 'success')
            self.setTaskBar("镜像推送成功")
            return {"suc": True, "msg": f"镜像 {image_name} 推送成功"}
        except Exception as e:
            print(f"错误信息{e}")
            return {"suc": False, "msg": f"推送镜像时发生错误: {str(e)}"}
        finally:
            if need_close and ssh:
                ssh.close()


if __name__ == '__main__':
    start(Api, url='./dist/index.html', debug=True)

    # sudo docker run --init --sig-proxy=false --name nextcloud-aio-mastercontainer --restart always --publish 80:80 --publish 8080:8080 --publish 8443:8443 --volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config --volume /var/run/docker.sock:/var/run/docker.sock:ro ghcr.io/nextcloud-releases/all-in-one:latest
